{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# MultiPolygons\n",
    "\n",
    "The MultiPolygons glyphs is modeled closely on the [GeoJSON spec](https://tools.ietf.org/html/rfc7946#section-3.1.6) for `Polygon` and `MultiPolygon`. The data that are used to construct `MultiPolygons` are nested 3 deep. In the top level of nesting, each item in the list represents a `MultiPolygon` - an entity like a state or a contour level. Each `MultiPolygon` is composed of `Polygons` representing different parts of the `MultiPolygon`. Each `Polygon` contains a list of coordinates representing the exterior bounds of the `Polygon` followed by lists of coordinates of any holes contained within the `Polygon`. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Polygon with no holes\n",
    "\n",
    "We'll start with one square with bottom left corner at (1, 3) and top right corner at (2, 4). The simple case of one `Polygon` with no holes is represented in geojson as follows:\n",
    "\n",
    "```geojson\n",
    " {\n",
    "     \"type\": \"Polygon\",\n",
    "     \"coordinates\": [\n",
    "         [\n",
    "             [1, 3],\n",
    "             [2, 3],\n",
    "             [2, 4],\n",
    "             [1, 4],\n",
    "             [1, 3]\n",
    "         ]\n",
    "     ]\n",
    " }\n",
    "```\n",
    "\n",
    "In geojson this list of coordinates is nested 1 deep to allow for passing lists of holes within the polygon. In `bokeh` (using `MultiPolygon`) the coordinates for this same polygon will be nested 3 deep to allow space for other entities and for other parts of the `MultiPolygon`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, output_notebook, show\n",
    "\n",
    "output_notebook()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')\n",
    "p.multi_polygons(xs=[[[[1, 2, 2, 1, 1]]]],\n",
    "                 ys=[[[[3, 3, 4, 4, 3]]]])\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that in the geojson `Polygon` always starts and ends at the same point and that the direction in which the `Polygon` is drawn (winding) must be counter-clockwise. In `bokeh` we don't have these two restrictions, the direction doesn't matter, and the polygon will be closed even if the starting end ending point are not the same."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')\n",
    "p.multi_polygons(xs=[[[[1, 1, 2, 2]]]],\n",
    "                 ys=[[[[3, 4, 4, 3]]]])\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Polygon with holes\n",
    "\n",
    "Now we'll add some holes to the square polygon defined above. We'll add a triangle in the lower left corner and another in the upper right corner. In geojson this can be represented as follows:\n",
    "\n",
    "```geojson\n",
    " {\n",
    "     \"type\": \"Polygon\",\n",
    "     \"coordinates\": [\n",
    "         [\n",
    "             [1, 3],\n",
    "             [2, 3],\n",
    "             [2, 4],\n",
    "             [1, 4],\n",
    "             [1, 3]\n",
    "         ],\n",
    "         [\n",
    "             [1.2, 3.2],\n",
    "             [1.6, 3.6],\n",
    "             [1.6, 3.2],\n",
    "             [1.2, 3.2]\n",
    "         ],\n",
    "         [\n",
    "             [1.8, 3.8],\n",
    "             [1.8, 3.4],\n",
    "             [1.6, 3.8],\n",
    "             [1.8, 3.8]\n",
    "         ]\n",
    "     ]\n",
    " }\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once again notice that the direction in which the polygons are drawn doesn't matter and the last point in a polygon does not need to match the first. Hover over the holes to demonstrate that they aren't considered part of the `Polygon`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')\n",
    "p.multi_polygons(xs=[[[ [1, 2, 2, 1], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ]]],\n",
    "                 ys=[[[ [3, 3, 4, 4], [3.2, 3.6, 3.2], [3.4, 3.8, 3.8] ]]])\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## MultiPolygon\n",
    "\n",
    "Now we'll examine a `MultiPolygon`. A `MultiPolygon` is composed of different parts each of which is a `Polygon` and each of which can have or not have holes. To create a `MultiPolygon` from the `Polygon` that we are using above, we'll add a triangle below the square with holes. Here is how this shape would be represented in geojson:\n",
    "\n",
    "\n",
    "```geojson\n",
    " {\n",
    "     \"type\": \"MultiPolygon\",\n",
    "     \"coordinates\": [\n",
    "         [\n",
    "             [\n",
    "                 [1, 3],\n",
    "                 [2, 3],\n",
    "                 [2, 4],\n",
    "                 [1, 4],\n",
    "                 [1, 3]\n",
    "             ],\n",
    "             [\n",
    "                 [1.2, 3.2],\n",
    "                 [1.6, 3.6],\n",
    "                 [1.6, 3.2],\n",
    "                 [1.2, 3.2]\n",
    "             ],\n",
    "             [\n",
    "                 [1.8, 3.8],\n",
    "                 [1.8, 3.4],\n",
    "                 [1.6, 3.8],\n",
    "                 [1.8, 3.8]\n",
    "             ]\n",
    "         ],\n",
    "         [\n",
    "             [\n",
    "                 [3, 1],\n",
    "                 [4, 1],\n",
    "                 [3, 3],\n",
    "                 [3, 1]\n",
    "             ]\n",
    "         ]\n",
    "     ]\n",
    " }\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')\n",
    "p.multi_polygons(xs=[[[ [1, 1, 2, 2], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ], [ [3, 4, 3] ]]],\n",
    "                 ys=[[[ [4, 3, 3, 4], [3.2, 3.2, 3.6], [3.4, 3.8, 3.8] ], [ [1, 1, 3] ]]])\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is important to understand that the `Polygons` that make up this `MultiPolygon` are part of the same entity. It can be helpful to think of representing physically separate areas that are part of the same entity such as the islands of Hawaii."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## MultiPolygons\n",
    "\n",
    "Finally, we'll take a look at how we can represent a list of `MultiPolygons`. Each `Mulipolygon` represents a different entity. In geojson this would be a `FeatureCollection`:\n",
    "\n",
    "```geojson\n",
    "{\n",
    "    \"type\": \"FeatureCollection\",\n",
    "    \"features\": [\n",
    "    {\n",
    "        \"type\": \"Feature\",\n",
    "        \"properties\": {\n",
    "            \"fill\": \"blue\"\n",
    "        },\n",
    "        \"geometry\": {\n",
    "            \"type\": \"MultiPolygon\",\n",
    "            \"coordinates\": [\n",
    "                [\n",
    "                    [\n",
    "                        [1, 3],\n",
    "                        [2, 3],\n",
    "                        [2, 4],\n",
    "                        [1, 4],\n",
    "                        [1, 3]\n",
    "                    ],\n",
    "                    [\n",
    "                        [1.2, 3.2],\n",
    "                        [1.6, 3.6],\n",
    "                        [1.6, 3.2],\n",
    "                        [1.2, 3.2]\n",
    "                    ],\n",
    "                    [\n",
    "                        [1.8, 3.8],\n",
    "                        [1.8, 3.4],\n",
    "                        [1.6, 3.8],\n",
    "                        [1.8, 3.8]\n",
    "                    ]\n",
    "                ],\n",
    "                [\n",
    "                    [\n",
    "                        [3, 1],\n",
    "                        [4, 1],\n",
    "                        [3, 3],\n",
    "                        [3, 1]\n",
    "                    ]\n",
    "                ]\n",
    "            ]\n",
    "        }\n",
    "    },\n",
    "    {\n",
    "        \"type\": \"Feature\",\n",
    "        \"properties\": {\n",
    "            \"fill\": \"red\"\n",
    "        },\n",
    "         \"geometry\": {\n",
    "            \"type\": \"Polygon\",\n",
    "            \"coordinates\": [\n",
    "                [\n",
    "                    [1, 1],\n",
    "                    [2, 1],\n",
    "                    [2, 2],\n",
    "                    [1, 2],\n",
    "                    [1, 1]\n",
    "                ],\n",
    "                [\n",
    "                    [1.3, 1.3],\n",
    "                    [1.3, 1.7],\n",
    "                    [1.7, 1.7],\n",
    "                    [1.7, 1.3]\n",
    "                    [1.3, 1.3]\n",
    "                ]\n",
    "            ]\n",
    "        }\n",
    "    }\n",
    "]}\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = figure(plot_width=300, plot_height=300, tools='hover,tap,wheel_zoom,pan,reset,help')\n",
    "p.multi_polygons(\n",
    "    xs=[\n",
    "        [[ [1, 1, 2, 2], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ], [ [3, 3, 4] ]],\n",
    "        [[ [1, 2, 2, 1], [1.3, 1.3, 1.7, 1.7] ]]],\n",
    "    ys=[\n",
    "        [[ [4, 3, 3, 4], [3.2, 3.2, 3.6], [3.4, 3.8, 3.8] ], [ [1, 3, 1] ]],\n",
    "        [[ [1, 1, 2, 2], [1.3, 1.7, 1.7, 1.3] ]]],\n",
    "    color=['blue', 'red'])\n",
    "show(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using MultiPolygons glyph directly"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.models import ColumnDataSource, Plot, LinearAxis, Grid\n",
    "from bokeh.models.glyphs import MultiPolygons\n",
    "from bokeh.models.tools import TapTool, WheelZoomTool, ResetTool, HoverTool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "source = ColumnDataSource(dict(\n",
    "    xs=[\n",
    "        [\n",
    "            [ \n",
    "                [1, 1, 2, 2], \n",
    "                [1.2, 1.6, 1.6], \n",
    "                [1.8, 1.8, 1.6] \n",
    "            ], \n",
    "            [ \n",
    "                [3, 3, 4] \n",
    "            ]\n",
    "        ],\n",
    "        [\n",
    "            [ \n",
    "                [1, 2, 2, 1], \n",
    "                [1.3, 1.3, 1.7, 1.7] \n",
    "            ]\n",
    "        ]\n",
    "    ],\n",
    "    ys=[\n",
    "        [\n",
    "            [ \n",
    "                [4, 3, 3, 4], \n",
    "                [3.2, 3.2, 3.6], \n",
    "                [3.4, 3.8, 3.8] \n",
    "            ], \n",
    "            [ \n",
    "                [1, 3, 1] \n",
    "            ]\n",
    "        ],\n",
    "        [\n",
    "            [ \n",
    "                [1, 1, 2, 2], \n",
    "                [1.3, 1.7, 1.7, 1.3] \n",
    "            ]\n",
    "        ]\n",
    "    ],\n",
    "    color=[\"blue\", \"red\"],\n",
    "    label=[\"A\", \"B\"]\n",
    "))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By looking at the dataframe for this `ColumnDataSource` object, we can see that each `MultiPolygon` is represented by one row. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "source.to_df()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hover = HoverTool(tooltips=[(\"Label\", \"@label\")])\n",
    "plot = Plot(plot_width=300, plot_height=300, tools=[hover, TapTool(), WheelZoomTool()])\n",
    "\n",
    "glyph = MultiPolygons(xs=\"xs\", ys=\"ys\", fill_color='color')\n",
    "plot.add_glyph(source, glyph)\n",
    "\n",
    "xaxis = LinearAxis()\n",
    "plot.add_layout(xaxis, 'below')\n",
    "\n",
    "yaxis = LinearAxis()\n",
    "plot.add_layout(yaxis, 'left')\n",
    "\n",
    "plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))\n",
    "plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))\n",
    "\n",
    "show(plot)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using numpy arrays with MultiPolygons\n",
    "Numpy arrays can be used instead of python native lists. In the following example, we'll generate concentric circles and used them to make rings. Similar methods could be used to generate contours. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "from bokeh.palettes import Viridis10 as palette"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def circle(radius):\n",
    "    angles = np.linspace(0, 2*np.pi, 100)\n",
    "    return {'x': radius*np.sin(angles), 'y': radius*np.cos(angles), 'radius': radius}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "radii = np.geomspace(1, 100, 10)\n",
    "source = dict(xs=[], \n",
    "              ys=[], \n",
    "              color=[palette[i] for i in range(10)], \n",
    "              outer_radius=radii)\n",
    "\n",
    "for i, r in enumerate(radii):\n",
    "    exterior = circle(r)\n",
    "    if i == 0:\n",
    "        polygon_xs = [exterior['x']]\n",
    "        polygon_ys = [exterior['y']]\n",
    "    else:\n",
    "        hole = circle(radii[i-1])\n",
    "\n",
    "        polygon_xs = [exterior['x'], hole['x']]\n",
    "        polygon_ys = [exterior['y'], hole['y']]\n",
    "\n",
    "    source['xs'].append([polygon_xs])\n",
    "    source['ys'].append([polygon_ys])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = figure(plot_width=300, plot_height=300, \n",
    "           tools='hover,tap,wheel_zoom,pan,reset,help',\n",
    "           tooltips=[(\"Outer Radius\", \"@outer_radius\")])\n",
    "\n",
    "p.multi_polygons('xs', 'ys', fill_color='color', source=source)\n",
    "show(p)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
